<?php
/**
 * Класс обеспечивает работу отладочной консоли
 *
 * @author Александр Егоров <slamer86@gmail.com>
 * @version 1.0
 */

class Service_Debug {

  public static $handDebugOff = false;
	public static $objDebug;
  public $arrData = array();
  public $arrUserData = array();

  public static function instance() {
    if (!isset(self::$objDebug)) {
      self::$objDebug = new Service_Debug();
    }
    return self::$objDebug;
  }


  /**
   * Добавляем переменные в Debuger
   * 
   * @param $functionName - Имя функции в которой вызывается
   * @param несколько переменных (любого типа) для добавления в Debuger 
   */
  public static function log($functionName) {
  	if (DEBUG && !self::$handDebugOff) {
	    if (!isset(self::$objDebug)) {
	      self::$objDebug = new Service_Debug();
	    }
	  	$arrArgs = func_get_args();
			$i = sizeof(self::$objDebug->arrUserData);
			self::$objDebug->arrUserData[$i]['function'] = $functionName;
			$k = 0;
			foreach ($arrArgs as $arg) {
				if ($k++) self::$objDebug->arrUserData[$i]['args'][] = $arg;
			}
  	}
  }

  /**
   * Записываем отладочную информацию в Debuger
   */
  public static function setDebug() {
  	if (DEBUG && !self::$handDebugOff) {
	    if (!isset(self::$objDebug)) {
	      self::$objDebug = new Service_Debug();
	    }	    
	    $debugTrace = debug_backtrace();
	    unset($debugTrace[0]);
  
	    $arrDebugLast = end($debugTrace);
	    if ($arrDebugLast['function'] == 'require_once') unset($debugTrace[sizeof($debugTrace)]);
	    $add = self::setChildrenDebug($debugTrace);
	    if ($add) {
	      
  	  	//$i = sizeof(self::$objDebug->arrData);
  	  	self::$objDebug->arrData[]['stack'] = $debugTrace;
  	  	//unset(self::$objDebug->arrData[$i][0]);
	    }
  	}
  }
  
  /**
   * Добавляем вложенности в debuger
   * 
   * @param $oldDebugTrace
   */
  private static function setChildrenDebug($oldDebugTrace) {
  	if (!DEBUG || self::$handDebugOff) return false;
    $debugTrace = array_reverse($oldDebugTrace);
    
    $add = 1;
  	if (sizeof(self::$objDebug->arrData)) {
  	  $key = sizeof(self::$objDebug->arrData) - 1; 
  	  $data = self::$objDebug->arrData[$key];
      if (isset($debugTrace[0]['class']) && isset($debugTrace[0]['function']) && $data['stack'][1]['class'] == $debugTrace[0]['class'] && $data['stack'][1]['function'] == $debugTrace[0]['function']) {
        if (isset(self::$objDebug->arrData[$key]['children'])) {
          $key2 = sizeof(self::$objDebug->arrData[$key]['children']) - 1; 
  	      $data2 = self::$objDebug->arrData[$key]['children'][$key2];
            if (isset($debugTrace[1]['class']) && isset($debugTrace[1]['function']) && $data2['stack'][1]['class'] == $debugTrace[1]['class'] && $data2['stack'][1]['function'] == $debugTrace[1]['function']) {
              if (isset(self::$objDebug->arrData[$key]['children'][$key2]['children'])) {
                  $key3 = sizeof(self::$objDebug->arrData[$key]['children'][$key2]['children']) - 1; 
  	              $data3 = self::$objDebug->arrData[$key]['children'][$key2]['children'][$key3];
                  if (isset($debugTrace[2]['class']) && isset($debugTrace[2]['function']) && $data3['stack'][1]['class'] == $debugTrace[2]['class'] && $data3['stack'][1]['function'] == $debugTrace[2]['function']) {
                    if (isset(self::$objDebug->arrData[$key]['children'][$key2]['children'][$key3]['children'])) {
                        $key4 = sizeof(self::$objDebug->arrData[$key]['children'][$key2]['children'][$key3]['children']) - 1; 
        	              $data4 = self::$objDebug->arrData[$key]['children'][$key2]['children'][$key3]['children'][$key4];
                        if (isset($debugTrace[3]['class']) && isset($debugTrace[3]['function']) && $data4['stack'][1]['class'] == $debugTrace[3]['class'] && $data4['stack'][1]['function'] == $debugTrace[3]['function']) {
                          if (isset(self::$objDebug->arrData[$key]['children'][$key2]['children'][$key3]['children'][$key4]['children'])) {
                              $key5 = sizeof(self::$objDebug->arrData[$key]['children'][$key2]['children'][$key3]['children'][$key4]['children']) - 1; 
              	              $data5 = self::$objDebug->arrData[$key]['children'][$key2]['children'][$key3]['children'][$key4]['children'][$key5];
                              if (isset($debugTrace[4]['class']) && isset($debugTrace[4]['function']) && $data5['stack'][1]['class'] == $debugTrace[4]['class'] && $data5['stack'][1]['function'] == $debugTrace[4]['function']) {
                                if (isset(self::$objDebug->arrData[$key]['children'][$key2]['children'][$key3]['children'][$key4]['children'][$key5]['children'])) {
                                    $key6 = sizeof(self::$objDebug->arrData[$key]['children'][$key2]['children'][$key3]['children'][$key4]['children'][$key5]['children']) - 1; 
                    	              $data6 = self::$objDebug->arrData[$key]['children'][$key2]['children'][$key3]['children'][$key4]['children'][$key5]['children'][$key6];
                                    if (isset($debugTrace[5]['class']) && isset($debugTrace[5]['function']) && $data6['stack'][1]['class'] == $debugTrace[5]['class'] && $data6['stack'][1]['function'] == $debugTrace[5]['function']) {
                                      if ($add) {
                                        self::$objDebug->arrData[$key]['children'][$key2]['children'][$key3]['children'][$key4]['children'][$key5]['children'][]['stack'] = $oldDebugTrace;
                                        $add = 0;
                                      }  
                                    }
                                }                                         
                                if ($add) {
                                  self::$objDebug->arrData[$key]['children'][$key2]['children'][$key3]['children'][$key4]['children'][$key5]['children'][]['stack'] = $oldDebugTrace;
                                  $add = 0;
                                }  
                              }
                          }                              
                          if ($add) {
                            self::$objDebug->arrData[$key]['children'][$key2]['children'][$key3]['children'][$key4]['children'][]['stack'] = $oldDebugTrace;
                            $add = 0;
                          }  
                        }
                    }                    
                    if ($add) {
                      self::$objDebug->arrData[$key]['children'][$key2]['children'][$key3]['children'][]['stack'] = $oldDebugTrace;
                      $add = 0;
                    }  
                  }
              }
              if ($add) {
                self::$objDebug->arrData[$key]['children'][$key2]['children'][]['stack'] = $oldDebugTrace;
                $add = 0;
              }  
            }
        }
        if ($add) {
          self::$objDebug->arrData[$key]['children'][]['stack'] = $oldDebugTrace;
          $add = 0;
        }  
      }
    }      
    return $add;  
  }  
  
  /**
   * Получаем всю информацию из Debugera
   * 
   * @param $addInfo - массив с дополнительной информацией которую надо добавить к тому что есть
   */
  public function getDebug($addInfo = null) {
  	if (!DEBUG || self::$handDebugOff) return array();
	  if ($addInfo) self::log('Result', $addInfo);
		$arrDebug['trace'] = $this->getDebugText();
		return $arrDebug;
  	
  }

  /**
   * Генерируем HTML для отладочной информации
   */
  public function getDebugText() {
  	if (!DEBUG || self::$handDebugOff) return '';
  	$retText = '';
  		$retText .= '<h3 class="debugCollapse">+ $_GET</h3>';
			$retText .= '<div class="debugHidden">';
				$retText .= '<pre>';
	  		$retText .= $this->makeCollapseForArray($_GET);
	  		$retText .= '</pre>';
	  	$retText .= '</div>';
			$retText .= '<h3 class="debugCollapse">+ $_POST</h3>';
			$retText .= '<div class="debugHidden">';
				$retText .= '<pre>';
		  	$retText .= $this->makeCollapseForArray($_POST);
		  	$retText .= '</pre>';
		  $retText .= '</div>';
			$retText .= '<h3 class="debugCollapse">+ $_COOKIE</h3>';
			$retText .= '<div class="debugHidden">';
				$retText .= '<pre>';
		  	$retText .= $this->makeCollapseForArray($_COOKIE);
		  	$retText .= '</pre>';
		  $retText .= '</div>';		  
	  	
	  $retText .= $this->getTreeStack($this->arrData);
  	
  	if (sizeof($this->arrUserData)) {
  		$retText .= '<br/>';
	  	$retText .= '<h3>Пользовательские данные:</h3>';
	  		foreach ($this->arrUserData as $data) {
					$retText .= '<h3 class="debugCollapse">+ ' . $data['function'] . '</h3>';
					$retText .= '<div class="debugHidden">';
					$i = 0;
					foreach ($data['args'] as $arg) {
						$retText .= '<p class="debugCollapse">+ Данные ' . ++$i . '</p>';
						$retText .= '<div class="debugHidden">';
							$retText .= '<pre>';
		  				$retText .= $this->makeCollapseForArray($arg);
		  				$retText .= '</pre>';
		  			$retText .= '</div>';
					}
		  		$retText .= '</div>';
		  	}
  	}
	  return $retText;
  }
  
  /**
   * Формируем древовидный стек
   * 
   * @param $arrData - Данные
   * @param $prefix - Сдвиг для вложенности
   */
  public function getTreeStack($arrData, $prefix = '') {
  	if (!DEBUG || self::$handDebugOff) return '';
    $retText = '';
    $i = 0;    
		$arrSystemFunctions = array(
			'User_Session::<b>instance</b>',
		  'User_Session-><b>impress</b>',
		  'User_Session::<b>deleteBadSessions</b>',
		  'User_User::<b>setZeroStatusByTimeOut</b>',
		  ''
		);
  	foreach ($arrData as $key => $arrStackFull) {
  	  $arrStack = $arrStackFull['stack'];
    		$className = $arrStack[1]['class'];
    		if (isset($arrStack[2]['function'])) $prevFuncName = $arrStack[2]['function'];
    		else $prevFuncName = '';
    		$funcName = $arrStack[1]['class'] . $arrStack[1]['type'] . $arrStack[1]['function'];
    		if ($arrStack[1]['function'] == '__construct') $funcFullName ='<b>Создаем объект</b> ' . $arrStack[1]['class'];
    		else $funcFullName = $arrStack[1]['class'] . $arrStack[1]['type'] . '<b>' . $arrStack[1]['function'] . '</b>';
    		$arrStack = array_reverse($arrStack);
    		unset($arrStack[0]);
    		//if ($arrStack[1]['function'] == 'action') unset($arrStack[1]);
    		$arrStackTemp = array();
    		foreach ($arrStack as $stack) {
    			$arrStackTemp[] = $stack;
    		}
    		$arrStack = $arrStackTemp;
  
    		$system = 0;	  		

    		if (in_array($funcFullName, $arrSystemFunctions)) $system = 1;
    		if (!$system) {
    			$retText .= '<h3 class="debugCollapse' . ($system ? ' debugSystem' : '') . '">' . $prefix . '+ Стэк ' . ++$i . ' - ' . $funcFullName . '</h3>';
  	  		$retText .= '<pre class="debugHidden' . ($system ? ' debugSystem' : '') . '">';
          	$stackSize = sizeof($arrStack);
        		$m = 0;
        		foreach ($arrStack as $arrFunction) {
        		  $className = isset($arrFunction['class']) ? $arrFunction['class'] . ' ' : '';
        			//$className = isset($arrFunction['object']) ? get_class($arrFunction['object']) . ' ' : '';
        			$retText .= '<p ' . (($m >= $stackSize - 1) ? 'class="debugCollapse"' : '') . '>Функция: ' . $className . (isset($arrFunction['type']) ? $arrFunction['type'] . ' ' : '') . '<b>' . $arrFunction['function'] . '()</b></p>';
        			$retText .= '<div class="debugHidden">';
        			if ($m >= $stackSize - 1) {
        				$retText .= isset($arrFunction['file']) ? '<p>файл: ' . $arrFunction['file'] . ' (' . $arrFunction['line'] . ')</p>' : '';
        				if (isset($arrFunction['object'])){
        					$retText .= '<p class="debugCollapse"><b>+ Объект</b></p>';
        					$retText .= '<div class="debugHidden">';
        						$retText .= print_r($arrFunction['object'], true);
        					$retText .= '</div>';
        				}
        				if (sizeof($arrFunction['args'])) {
        					$retText .= '<p class="debugCollapse"><b>+ Аргументы</b></p>';
        					$retText .= '<div class="debugHidden">';
        					$k = 0;
        					foreach ($arrFunction['args'] as $arg) {
        					  $argType = gettype($arg);
        					  if ($argType == 'object' || ($argType == 'array')) {
          						$retText .= '<p class="debugCollapse"><b>+ Параметр ' . ++$k . ' (' . $argType . ($argType == 'object' ? ' - ' . get_class($arg) : '') . ')</b></p>';
          						$retText .= '<div class="debugHidden">';
          							$retText .= $this->makeCollapseForArray($arg);
          						$retText .= '</div>';
        					  }
        					  else $retText .= '<p>' . ($argType == 'string' ? '"' : '') . $arg . ($argType == 'string' ? '"' : '') . ' <b>(' . $argType . ')</b></p>';
        					}
        					$retText .= '</div>';
        			  }	  
        			}
        			$m++;
        			$retText .= '</div>';					
        		}
        		if (isset($arrStackFull['children'])) $retText .= $this->getTreeStack($arrStackFull['children'], $prefix/* . '&nbsp;&nbsp;'*/);
  				$retText .= '</pre>';
    		}
  	}		
  	return $retText;
  }
  
  /**
   * Выводим отладочную информацию в виде HTML 
   */
  public function echoDebug() {
  	if (!DEBUG || self::$handDebugOff) return false;
  	echo '<div id="debug" class="debug" style="display:none;">';
  		echo $this->getDebugText();
	  echo '</div>';
  }  
  
  /**
   * Генерируем HTML-текст для параметров 
   * 
   * @param $arrData - Данные для генерации
   */
  public function makeCollapseForArray($arrData) {
  	if (!DEBUG || self::$handDebugOff) return '';
    $retText = '';
    if (is_array($arrData)) {
      foreach ($arrData as $key => $value) {
        if (is_array($value)) {
          $continue = 1;
        }
      }
      if (isset($continue)) { // Если среди данных есть массивы, то выводим рекурсивно
        foreach ($arrData as $key => $value) {
          if (is_array($value)) {
      			$retText .= '<p class="debugCollapse">+ ' . $key . '</p>';
      			$retText .=  '<div class="debugHidden">';
      				$retText .= $this->makeCollapseForArray($value);
      			$retText .= '</div>';  
          }
          else $retText .= $key . ': ' . $value . '<br>'; 
        }   
      }
      else { // Если массивов нет, то выводим данные через print_r
        $retText .= '<pre>';
        $retText .= print_r($arrData, true);
        $retText .= '</pre>';
      }
    }
    elseif (is_string($arrData)) $retText .= $arrData;
    else { // Любой другой тип данных (объекты)
      $retText .= '<pre>';
      $retText .= print_r($arrData, true);
      $retText .= '</pre>';
    }
    return $retText;
  }
}
?>